package orbit

import (
	"sync"
)

type LocationCenter struct {
	LocationPool      sync.Pool
	BlockLocationPool sync.Pool
}

var (
	LocationCM = &LocationCenter{
		LocationPool:      sync.Pool{},
		BlockLocationPool: sync.Pool{},
	}
	DirectionBlockMap = map[Direction][]Point{
		TopDirection:         {0, 1},
		BottomDirection:      {0, -1},
		LeftDirection:        {-1, 0},
		RightDirection:       {1, 0},
		LeftTopDirection:     {-1, 1},
		RightTopDirection:    {1, 1},
		LeftBottomDirection:  {-1, -1},
		RightBottomDirection: {1, -1},
	}
)

func init() {
	LocationCM.LocationPool.New = func() interface{} {
		return &Location{}
	}
	LocationCM.BlockLocationPool.New = func() interface{} {
		return &BlockLocation{}
	}
}

func (center *LocationCenter) EncodeLocation(location *Location) []byte {
	return EncodeLocation(int32(location.X), int32(location.Y))
}

func (center *LocationCenter) DecodeLocation(location []byte) *Location {
	l := center.LocationPool.Get().(*Location)
	x, y := DecodeLocation(location)
	l.X = Point(x)
	l.Y = Point(y)
	return l
}
func (center *LocationCenter) CenterBlock(gap Location, gapWrap Location, x Point, y Point) *BlockLocation {
	b := center.BlockLocationPool.Get().(*BlockLocation)
	b.Gap = gap
	b.GapWrap = gapWrap
	if b.Gap.X == 0 && b.GapWrap.X == 0 {
		b.Center.X = 0
		b.Center.Y = 0
		return b
	}
	var r Point
	r = x / b.Gap.X
	b.Center.X = r
	if r%2 == 0 {
		if x >= 0 {
			b.Center.X += 1
		} else {
			b.Center.X -= 1
		}
	}
	b.Center.X *= b.Gap.X

	r = y / b.Gap.Y
	b.Center.Y = r
	if r%2 == 0 {
		if y >= 0 {
			b.Center.Y += 1
		} else {
			b.Center.Y -= 1
		}
	}
	b.Center.Y *= b.Gap.Y
	return b
}

// 包含左和下，不包含右及上
func (center *LocationCenter) CenterBlockWithLocation(gap Location, gapWrap Location, location *Location) *BlockLocation {
	return center.CenterBlock(gap, gapWrap, location.X, location.Y)
}

func (center *LocationCenter) NextBlock(block *BlockLocation, start *Location, end *Location) (*BlockLocation, *Location) {
	x := end.X - start.X
	y := end.Y - start.Y
	l := center.LocationPool.Get().(*Location)

	xmin := block.Center.X - block.Gap.X
	xmax := block.Center.X + block.Gap.X

	ymin := block.Center.Y - block.Gap.Y
	ymax := block.Center.Y + block.Gap.Y

	direction := NoneDirection

	if end.X >= xmin && end.X < xmax {
		// 上下象限
		if end.Y < ymin {
			// 下
			direction = BottomDirection
		} else {
			// 上
			direction = TopDirection
		}
	} else if end.X >= ymin && end.Y < ymax {
		// 左右象限
		if end.X < xmin {
			// 左
			direction = LeftDirection
		} else {
			// 右
			direction = RightDirection
		}
	} else if end.X < xmin && end.Y < ymin {
		// 左下
		first := x/y*(ymin-start.Y) + start.X
		if first >= xmin {
			// 下
			direction = BottomDirection
		} else {
			// 左
			direction = LeftDirection
		}
	} else if end.X >= xmax && end.Y >= ymax {
		// 右上
		first := x/y*(ymax-start.Y) + start.X
		if first < xmax {
			// 上
			direction = TopDirection
		} else {
			// 右
			direction = RightDirection
		}
	} else if end.X < xmin && end.Y >= ymax {
		// 左上
		first := x/y*(ymax-start.Y) + start.X
		if first >= xmin {
			// 上
			direction = TopDirection
		} else {
			// 左
			direction = LeftDirection
		}
	} else if end.X >= xmax && end.Y < ymin {
		// 右下
		first := x/y*(ymin-start.Y) + start.X
		if first < xmax {
			// 下
			direction = BottomDirection
		} else {
			// 右
			direction = RightDirection
		}
	}
	switch direction {
	case TopDirection:
		l.Y = ymax
		l.X = Point(float64(x)/float64(y)*float64(l.Y-start.Y)) + start.X
	case BottomDirection:
		l.Y = ymin
		l.X = Point(float64(x)/float64(y)*float64(l.Y-start.Y)) + start.X
	case LeftDirection:
		l.X = xmin
		l.Y = Point(float64(y)/float64(x)*float64(l.X-start.X)) + start.Y
	case RightDirection:
		l.X = xmax
		l.Y = Point(float64(y)/float64(x)*float64(l.X-start.X)) + start.Y
	}

	return center.CenterBlockWithLocation(block.Gap, block.GapWrap, l), l
}

func (center *LocationCenter) IndexInBlock(block *BlockLocation, position []byte) int {
	location := center.DecodeLocation(position)
	index := block.Index(location)
	center.ReleaseLocation(location)
	return index
}

func (center *LocationCenter) NodeBlock(node string) *BlockLocation {
	block := center.BlockLocationPool.Get().(*BlockLocation)
	if node == "" {
		block.Center.X = 0
		block.Center.Y = 0
		block.Gap.X = 0
		block.GapWrap.X = 0
		return block
	}
	block.FromNode(node)
	return block
}

func (center *LocationCenter) ReleaseLocation(location *Location) {
	center.LocationPool.Put(location)
}

func (center *LocationCenter) ReleaseBlock(block *BlockLocation) {
	center.BlockLocationPool.Put(block)
}

func (center *LocationCenter) Distance(positions []*Location, index int) float64 {
	if positions == nil || len(positions) == 0 {
		return 0
	}
	distance := float64(0)
	var tmp *Location
	for i := index; i < len(positions); i++ {
		location := positions[i]
		if tmp != nil {
			distance += location.Distance(tmp)
		}
		tmp = location
	}
	return distance
}

func GetSubWithBlock(block *BlockLocation) string {
	return block.ToNode()
}

func GetBlockWithSub(sub string) *BlockLocation {
	block := LocationCM.BlockLocationPool.Get().(*BlockLocation)
	block.FromNode(sub)
	return block
}
